package com.novel.framework.web;

import com.novel.common.exception.DemoModeException;
import com.novel.common.exception.business.BusinessException;
import com.novel.framework.result.Result;
import com.novel.kaptcha.exception.*;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * 异常统一处理
 *
 * @author novel
 * @date 2020/3/2
 */
@Slf4j
@RestControllerAdvice
public class ExceptionController {

    /**
     * 捕捉UnauthorizedException
     *
     * @param e UnauthorizedException
     * @return 结果
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(UnauthorizedException.class)
    public Result handle401(UnauthorizedException e) {
        log.error("{},{}", "401", "权限不足" + e.getMessage());
        return new Result(401, false, "权限不足！", null);
    }

    /**
     * 权限不足异常
     *
     * @param e AuthenticationException
     * @return 结果
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(AuthenticationException.class)
    public Result handle401(AuthenticationException e) {
        log.error("{},{}", "401", "对不起,您无权限进行操作!" + e.getMessage());
        return new Result(401, false, "对不起,您无权限进行操作!", null);
    }

    /**
     * 捕捉shiro的异常
     *
     * @param e ShiroException
     * @return 结果
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ShiroException.class)
    public Result handle401(ShiroException e) {
        e.printStackTrace();
        log.error("{},{}", "401", "shiro的异常" + e.getMessage());
        return new Result(401, false, e.getMessage(), null);
    }

    /**
     * 业务异常
     *
     * @param e 业务异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public Result businessException(BusinessException e) {
        log.error("{},{}", "500", "业务异常" + e.getMessage());
        return new Result(500, false, e.getMessage(), null);
    }

    /**
     * 数据库异常
     *
     * @param e 数据库异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(SQLException.class)
    public Result sqlException(SQLException e) {
        e.printStackTrace();
        log.error("{},{}", "500", "数据库异常" + e.getMessage());
        return new Result(500, false, "数据库异常！", null);
    }

    /**
     * 数据库异常
     *
     * @param e 数据库异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public Result sqlIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
        e.printStackTrace();
        log.error("{},{}", "500", "数据库异常" + e.getMessage());
        return new Result(500, false, "数据库异常", null);
    }

    /**
     * 数据库异常
     *
     * @param e 数据库异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(DuplicateKeyException.class)
    public Result duplicateKeyException(DuplicateKeyException e) {
        e.printStackTrace();
        log.error("{},{}", "500", "数据库异常，主键重复" + e.getMessage());
        return new Result(500, false, "主键重复", null);
    }

    /**
     * 验证码异常
     *
     * @param e 验证码异常
     * @return 结果
     */
    @ResponseStatus(HttpStatus.VARIANT_ALSO_NEGOTIATES)
    @ExceptionHandler(KaptchaException.class)
    public Result kaptchaException(KaptchaException e) {
        log.error("{},{}", "500", "验证码异常" + e.getMessage());
        if (e instanceof KaptchaCacheException) {
            return new Result(500, false, "验证码服务异常，请联系管理员", null);
        } else if (e instanceof KaptchaIncorrectException) {
            return new Result(500, false, "验证码输入错误", null);
        } else if (e instanceof KaptchaIsEmptyException) {
            return new Result(500, false, "验证码不能为空", null);
        } else if (e instanceof KaptchaNotFoundException) {
            return new Result(500, false, "验证码不存在", null);
        } else if (e instanceof KaptchaRenderException) {
            return new Result(500, false, "验证码生成错误，请联系管理员", null);
        } else if (e instanceof KaptchaTimeoutException) {
            return new Result(500, false, "验证码已过时", null);
        } else {
            return new Result(500, false, "验证码错误", null);
        }
    }

    /**
     * 演示模式异常
     *
     * @param e 演示模式异常
     * @return 结果
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(DemoModeException.class)
    public Result demoModeException(DemoModeException e) {
        if (e.getMsg() != null && !e.getMsg().isEmpty()) {
            return Result.error(e.getMsg());
        }
        return Result.error("演示模式，不允许操作");
    }

    /**
     * 数据绑定校验异常
     *
     * @param e 异常
     * @return 返回消息
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result bindException(BindException e) {
        e.printStackTrace();
        log.error("{},{}", "400", "数据绑定校验异常" + e.getMessage());
        return getResultView("参数错误", e);
    }

    /**
     * 请求方式错误
     *
     * @param e 异常
     * @return 返回消息
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result requestMethodErrorException(HttpRequestMethodNotSupportedException e) {
        log.error("{},{}", "500", "请求方式不正确：" + e.getMessage());
        return new Result(500, false, "请求方式不正确：" + e.getMessage(), null);
    }


    /**
     * 捕捉其他所有异常
     *
     * @param request request
     * @param e       异常
     * @return 返回消息
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result globalException(HttpServletRequest request, Exception e) {
        e.printStackTrace();
        log.error("{},{}", "500", "未知异常" + e.getMessage());
        return new Result(getStatus(request).value(), false, e.getMessage(), null);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }


    /**
     * 参数异常
     *
     * @param msg
     * @param e
     * @return
     */
    private Result getResultView(String msg, BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        Set<BindingResultObject> errorMessage = new HashSet<>();
        allErrors.forEach(item -> errorMessage.add(BindingResultObject.builder().build().setMessage(item.getDefaultMessage()).setField(((DefaultMessageSourceResolvable) Objects.requireNonNull(item.getArguments())[0]).getDefaultMessage())));
        return Result.builder().msg(msg).code(400).data(errorMessage).build();
    }
}

@Builder
@Data
class BindingResultObject implements Serializable {
    private String field;
    private String message;

    public BindingResultObject setField(String field) {
        this.field = field;
        return this;
    }

    public BindingResultObject setMessage(String message) {
        this.message = message;
        return this;
    }
}